package com.xiaoc.bulldozer.springboot.config.web.interceptor;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Lists;
import com.xc.framework.cache.manager.CacheManager;
import com.xc.framework.common.exception.BusinessException;
import com.xc.framework.common.thread.ThreadContext;
import com.xiaoc.bulldozer.annotate.Duplicate;
import com.xiaoc.bulldozer.common.Constant;
import com.xiaoc.bulldozer.common.ResponseCode;
import com.xiaoc.bulldozer.common.dto.WeixinUserDto;
import com.xiaoc.bulldozer.common.util.IPAddressUtil;
import com.xiaoc.bulldozer.common.util.UrlMappingUtils;
import com.xiaoc.bulldozer.facade.WeixinUserFacade;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

public class UserSessionHandler implements HandlerInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(UserSessionHandler.class);

    @Resource(name = "weixinUserFacade")
    private WeixinUserFacade weixinUserFacade;

    @Resource(name = "remoteCacheManager")
    private CacheManager remoteCacheManager;

    @Value("${pay.ips}")
    private String payIps;

    private static Cache<String, String> resourceMap = CacheBuilder.newBuilder()
            .expireAfterWrite(10, TimeUnit.SECONDS).removalListener(notification -> logger.info("key [{}] ,cause [{}]",notification.getKey(),notification.getCause().toString()))
            .build();

    private List<String> skipPathList = Lists.newArrayList("/pay/entPay", "/pay/accountBalance", "/pay/userInfo");
    /**
     * 在业务处理器处理请求之前被调用 如果返回false 从当前的拦截器往回执行所有拦截器的afterCompletion(),再退出拦截器链 如果返回true
     * 执行下一个拦截器,直到所有的拦截器都执行完毕 再执行被拦截的Controller 然后进入拦截器链,
     * 从最后一个拦截器往回执行所有的postHandle() 接着再从最后一个拦截器往回执行所有的afterCompletion()
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String requestURI = request.getRequestURI();

        if (skipPathList.contains(requestURI)) {
            String ipAddresses = IPAddressUtil.getIPAddress(request);
            logger.info("ipAddresses = {}", ipAddresses);
            if (!payIps.contains(ipAddresses)) {
                throw new BusinessException(ResponseCode.REQUEST_FORBIDDEN.getCode(), ResponseCode.REQUEST_FORBIDDEN.getMsg());
            }
            return true;
        }
        MDC.put("ip",IPAddressUtil.getIPAddress(request));
        if (requestURI.equals("/advertise/getAdvertise")) {
            logger.info("无需登录[{}]",requestURI);
            return true;
        }

        String sessionId = request.getHeader(Constant.COOKIE_SESSION_ID);

        if (StringUtils.isBlank(sessionId)) {
            logger.warn("Header sessionId is null  and rquestURI:{}" ,requestURI);
            throw new BusinessException(ResponseCode.REQUEST_TIMEOUT.getCode(), "登录超时, 请重新登录");
        }

        WeixinUserDto weixinUserDto = weixinUserFacade.getWeixinUserDtoBySessionId(sessionId);

        if (weixinUserDto == null) {
            logger.warn("Fetch weixinUserDto is null for sessionId:" + sessionId + " and rquestURI:" + requestURI);
            throw new BusinessException(ResponseCode.REQUEST_TIMEOUT.getCode(), "登录超时, 请重新登录");
        }

        if (!weixinUserDto.isEnable()) {
            logger.warn("user forbidden for userId:" + weixinUserDto.getId() + " and rquestURI:" + requestURI);
            throw new BusinessException(ResponseCode.REQUEST_ERROR_USER_LOCKED.getCode(), "您的账户已经被禁用, 请联系系统管理员");
        }

        ThreadContext.put(Constant.COOKIE_SESSION_ID, sessionId);
        ThreadContext.put(Constant.START_TIME, System.currentTimeMillis());

        MDC.put("name",weixinUserDto.getId()+"."+ StringUtils.trimToEmpty(weixinUserDto.getNickName()));

        // 降低set memcached的频率
        long sessionTime = weixinUserDto.getSessionTime() == null ? Constant.DEFAULT_SESSION_TIME : weixinUserDto.getSessionTime().longValue();

        long intervalTime = System.currentTimeMillis() - sessionTime;

        if (intervalTime > (Constant.SESSION_TIMEOUT / Constant.SESSION_REFRESH_FREQUENCY)) {
            weixinUserFacade.putWeixinUserDto(sessionId, weixinUserDto);
        }

        Optional<Method> method = Optional.ofNullable(UrlMappingUtils.getMethodByUrl(requestURI));
        if(logger.isDebugEnabled()) {
            logger.debug("request.getRequestURI()：{}", requestURI);
            logger.debug("method---: {}",method);
        }

         method.ifPresent(m -> {
            Duplicate duplicate = m.getAnnotation(Duplicate.class);
            if (duplicate != null) {
                String resourceKey = weixinUserDto.getId() + ":" + requestURI;
                synchronized (resourceKey.intern()) {
                    boolean isDuplicateSubmit = duplicate.isDuplicateSubmit();
                    logger.info("resourceKey: {}, isDuplicateSubmit:{}", resourceKey, isDuplicateSubmit);
                    if (isDuplicateSubmit) {
                        ThreadContext.put(Constant.USER_ID, weixinUserDto.getId());
                        logger.info("校验重复提交: {}", resourceKey);

                        if (resourceMap.getIfPresent(resourceKey) != null) {
                            logger.info("已重复提交: {}", requestURI);
                            throw new BusinessException(ResponseCode.BUSINESS_ERROR.getCode(), "请勿重复操作");
                        }

                        logger.info("wxId:{},uri:{}", weixinUserDto.getId(), resourceKey);
                        resourceMap.put(resourceKey, "");
                    }
                }
            }
        });

        logger.info("last visit userId: [{}],[{}],[{}] , method: [{}]", weixinUserDto.getId(), requestURI, System.currentTimeMillis(), getFullMethodName(method));
        return true;
    }

    private String getFullMethodName(Optional<Method> method) {
        return method.map(it -> it.getDeclaringClass().getCanonicalName() + "." + it.getName()).orElse("");
    }

    /**
     * 在业务处理器处理请求执行完成后,生成视图之前执行的动作 可在modelAndView中加入数据，比如当前时间
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    }

    /**
     * 在DispatcherServlet完全处理完请求后被调用,可用于清理资源等
     * <p>
     * 当有拦截器抛出异常时,会从当前拦截器往回执行所有的拦截器的afterCompletion()
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

        Object userId = ThreadContext.get(Constant.USER_ID);
        if(userId != null) {
            String resourceKey = userId + ":" + request.getRequestURI();
            resourceMap.invalidate(resourceKey);
            ThreadContext.remove(Constant.USER_ID);
            if(logger.isDebugEnabled()){
                logger.debug("resourceKey:{}",resourceKey);
            }
        }

        /*Object startTime = ThreadContext.get(Constant.START_TIME);
        if(startTime != null) {
            ThreadContext.remove(Constant.START_TIME);
            logger.info("url:{}, cost:{}ms", request.getRequestURI(), System.currentTimeMillis() - (long) startTime);
        }*/
        MDC.clear();

    }
}
